#!/usr/bin/python
# -*- coding: utf-8 -*-

#This file is part of pyLot library.
#
# pyLot is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyLot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pyLot.  If not, see <http://www.gnu.org/licenses/>.

__author__ = u'Pierre Puiseux, Guillaume Baty'
__copyright__ = u"Copyright 2011-2012 (C) andheo, Université de Pau et des Pays de l'Adour"
__credits__ = [u'Pierre Puiseux', u'Guillaume Baty']
__license__ = "GNU Lesser General Public License"

u"""
gui_typedchoosers provides widgets specialized for Datum objects.

See make_chooser, make_selector
"""

__all__ = [
  'GuiSelector',
  'GuiBoolSelector',
  'GuiDefaultSelector',
  'GuiIntSelector',
  'GuiFloatSelector',
  'GuiUnicodeSelector',
  'GuiCoordinateSelector',
  'GuiUnfoldSelector',
  'GuiPathSelector',
  'GuiPredefUnicodeSelector',
  'GuiRadioUnicodeSelector',
  'GuiRadioBoolSelector',
  'make_selector',
  'get_paint_function',
  ]

from Qt4 import QtGui, QtCore
from Qt4.QtCore import SIGNAL

from traits.api import implements

from _pylot.qtgui.styles.policy import *
from _pylot.qtgui.tools import create_background_palette, get_icon
from _pylot.qtgui.custom import DEFAULT_CUSTOM
from ..interfaces.i_typedselector import ITypedSelector

from pyLot.core import (Coordinate, unicode2, Path, check_type, datum_name,
                        Datum)


def paint_unicode(view, painter, option, index, datum):
    check_type(u'datum', datum, Datum)
    try :
      alias = datum.getValueAlias(datum.value)
    except (AttributeError, KeyError):
      alias = unicode(datum.value)

    # if cell is selected, highlight it using current palette
    if (option.state & QtGui.QStyle.State_Selected):
      painter.fillRect(option.rect, option.palette.highlight())
      color_role = option.palette.HighlightedText
    else :
      painter.fillRect(option.rect, option.palette.base())
      color_role = QtGui.QPalette.Text
    style = QtGui.QApplication.style()

    style.drawItemText (painter, option.rect,
                        QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
                        option.palette, True, u' ' + alias, color_role)
    # Approche 2
    # painter.drawText(option.rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, alias)

    option.text = u''
    return True


class Undefined(object):
  pass

UNDEFINED = Undefined()

def unicode3(s):
  if s is None :
    return s
  elif isinstance(s, unicode) :
    return s
  elif isinstance(s, QtCore.QString):
    return unicode(s)
  else :
    raise ValueError, 'Only unicode, QString and None supported, got %r' % s

class GuiSelector (object):

  implements(ITypedSelector)

  def __init__(self, datum, custom=DEFAULT_CUSTOM):
    self._datum = datum
    self._datum.setUpdateMode(Datum.ModeDoNotUpdate)

    try :
      self.setPlaceholderText
    except AttributeError : # Qt < 4.7 or do not inherit from QLineEdit
      self.setPlaceholderText = self._setPlaceHolderText

    self.setValue(datum.value)
    self.setToolTip(unicode2(self._datum.tooltip))
    self.custom = custom

    enabled = self.custom.getOption('enabled', self, datum, None)
    if enabled is not None :
      self.setEnabled(enabled)



  def _setPlaceHolderText(self, s):
    # Used only for Qt < 4.7.
    # Replace excpected setPlaceholderText
    pass

  def contextMenuEvent(self, event) :
    action_reset_to_default = QtGui.QAction(get_icon(u'redo.png'),
      u'Reset to default value "%s"' % self._datum.default_value, self)
    action_set_as_default = QtGui.QAction(get_icon(u'reload.png'),
      u'Use "%s" as default value' % self.value(), self)

    action_reset_to_default.triggered.connect(self.actionResetToDefaultValue)
    action_set_as_default.triggered.connect(self.actionSetAsDefaultValue)

    menu = QtGui.QMenu()

    if self.value() != self._datum.default_value :
      menu.addSeparator()
      menu.addAction(action_set_as_default)
      menu.addAction(action_reset_to_default)
      menu.addSeparator()

    menu.exec_(event.globalPos())

  def actionSetAsDefaultValue(self):
    self._datum.default_value = self.getValue()

  def actionResetToDefaultValue(self):
    self.setValue(self._datum.default_value)

  def value(self):
    return self._datum.value

  def getValue(self):
    return self._datum.value

  def _valueChanged(self, value):
    if value != self._datum.value:
      self.setValue(value)
      self.valueChanged.emit(self.value())

  def setValue(self, value):
    raise NotImplementedError

  def setDefaultValue(self, value):
    raise NotImplementedError

  def setValueList(self, value_list):
    pass

  def error(self, error=u'Value cannot be converted'):
    qcolor = QtGui.QColor(255, 255, 150)
    brush = QtGui.QBrush(qcolor, QtCore.Qt.Dense2Pattern)
    palette = QtGui.QPalette()
    palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush)
    palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush)
    self.setPalette(palette)

  def undef(self, error=u'Value not defined'):
    self.error(error)

  def doubt(self):
    palette = create_background_palette(QtGui.QColor(255, 255, 150))
    self.setPalette(palette)

  def ok(self):
    palette = QtCore.QCoreApplication.instance().palette()
    self.setPalette(palette)

  def setState(self, state, msg=u''):
    if state is None :
      self.undef()
    elif state is True :
      self.ok()
    else :
      self.error()
    self.setToolTip(msg)

class GuiIntSelector (QtGui.QSpinBox, GuiSelector):

  nature = int

  def __init__(self, datum, datarange=(0, 1000000), custom=DEFAULT_CUSTOM):
    QtGui.QSpinBox.__init__(self)
    GuiSelector.__init__(self, datum, custom=custom)
    self.valueChanged[int].connect(self._valueChanged)
    self.setRange(*datarange)
    self.setValue(self._datum.value)


  def setValue(self, value):
    self._datum.value = value
    if value is None :
      QtGui.QSpinBox.setValue(self, 0)
      self.undef()
      self.setPrefix(u'*UNDEF* ')
      return
    self.ok()
    self.setPrefix(u'')
    QtGui.QSpinBox.setValue(self, value)


class GuiBoolSelector (QtGui.QCheckBox, GuiSelector):

  nature = bool
  valueChanged = QtCore.Signal(object)

  def __init__(self, datum, custom=DEFAULT_CUSTOM):
    QtGui.QCheckBox.__init__(self)
    GuiSelector.__init__(self, datum, custom=custom)
    self.stateChanged[int].connect(self._valueChanged)


  def setValue(self, value):
    if value == QtCore.Qt.Checked or value is True :
      self._datum.value = True
      self.setTristate(False)
      self.setCheckState(QtCore.Qt.Checked)
      self.ok()
    elif value == QtCore.Qt.Unchecked or value is False :
      self._datum.value = False
      self.setTristate(False)
      self.setCheckState(QtCore.Qt.Unchecked)
      self.ok()
    elif value is None :
      self.setTristate(True)
      self._datum.value = None
      self.setCheckState(QtCore.Qt.PartiallyChecked)
      self.undef()
    else :
      check_type(u'value', value, (int, bool), none=True)

  @classmethod
  def paint_cell(cls, view, painter, option, index, datum) :
    if (option.state & QtGui.QStyle.State_Selected):
      painter.fillRect(option.rect, option.palette.highlight());
      fg_color = option.palette.HighlightedText
    else :
      fg_color = QtGui.QPalette.Text
    style = QtGui.QApplication.style()

    if isinstance(datum.value, bool):
      style = QtGui.QApplication.style()
      opt = QtGui.QStyleOptionButton()
      opt.state = QtGui.QStyle.State_On if datum.value else QtGui.QStyle.State_Off
      opt.rect = option.rect
      style.drawControl(QtGui.QStyle.CE_CheckBox, opt, painter, None)
      return True
    else :
      return False

class GuiUnfoldSelector (GuiBoolSelector):
  def __init__(self, datum, custom=DEFAULT_CUSTOM):
    GuiBoolSelector.__init__(self, datum, custom=custom)
    self.setText(datum_name(datum))

class GuiUnicodeSelector (QtGui.QLineEdit, GuiSelector):

  nature = unicode
  valueChanged = QtCore.Signal(object)

  def __init__(self, datum, editable=True, check=None,
               custom=DEFAULT_CUSTOM):
    QtGui.QLineEdit.__init__(self)
    GuiSelector.__init__(self, datum, custom=custom)
    self.connect(self, SIGNAL('editingFinished()'), self.onEditingFinished)

    editable = self.custom.getOption(u'editable', self, datum, None)
    if editable is not None :
      self.setEditable(editable)

    self.check = self.custom.getOption(u'func_check', self, datum, check)

    shared_list = self.custom.getOption(u'shared_list', self, datum, [])
    if self._datum.value_list :
      shared_list += self._datum.value_list
    shared_list = [self.toUnicode(elem) for elem in set(shared_list)]

    self.completer_ = QtGui.QCompleter(shared_list)
    self.completer_.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
    self.completer_.setCompletionMode(QtGui.QCompleter.PopupCompletion)
    self.setCompleter(self.completer_)
    self.shared_list_model = self.completer_.model()

    self.onEditingFinished()

  def actionSetValue(self):
    alias = unicode(self.sender().text())
    self.setValue(alias)

  def contextMenuEvent(self, event) :
    action_reset_to_default = QtGui.QAction(get_icon(u'redo.png'),
      u'Reset to default value "%s"' % self._datum.default_value, self)
    action_set_as_default = QtGui.QAction(get_icon(u'reload.png'),
      u'Use "%s" as default value' % self.value(), self)

    action_reset_to_default.triggered.connect(self.actionResetToDefaultValue)
    action_set_as_default.triggered.connect(self.actionSetAsDefaultValue)

    menu = QtGui.QMenu()

    if self.value() != self._datum.default_value :
      menu.addSeparator()
      menu.addAction(action_set_as_default)
      menu.addAction(action_reset_to_default)
      menu.addSeparator()

    if self._datum.value_list :
      for value in self._datum.value_list :
        alias = self.toUnicode(value)
        action = QtGui.QAction(alias, self)
        action.triggered.connect(self.actionSetValue)
        menu.addAction(action)

    menu.exec_(event.globalPos())

  def onEditingFinished(self):
    text = unicode(self.text())
    if self._datum.value is None and not text :
      self.setValue(None)
    else :
      value = self.fromUnicode(text)
      self._valueChanged(value)

  def setEditable(self, state):
    self.setReadOnly(not state)

  def setValue(self, value):
    value = self._datum.value = self.fromUnicode(value)
    if value is None :
      self.clear()
      self.setPlaceholderText(u'valeur non définie')
      self.undef()
    else :
      self.setText(self.toUnicode(value))
      self.setPlaceholderText(u'chaîne vide')
      self.ok()

  def setDefaultValue(self, value):
    value = self._datum.default_value = self.fromUnicode(value)

  def setValueList(self, value_list):
    pass

  def toUnicode(self, value):
    try :
      alias = self._datum.getValueAlias(value)
    except (AttributeError, KeyError) :
      alias = value
    return unicode3(alias)

  def fromUnicode(self, alias):
    try :
      value = self._datum.getValueAlias(alias)
    except (AttributeError, KeyError) :
      value = alias
    return unicode3(value)

  @classmethod
  def paint_cell(cls, view, painter, option, index, datum) :
    return paint_unicode(view, painter, option, index, datum)


class GuiPredefUnicodeSelector (QtGui.QComboBox, GuiSelector):

  nature = unicode
  valueChanged = QtCore.Signal(object)

  def __init__(self, datum, custom=DEFAULT_CUSTOM):
    QtGui.QComboBox.__init__(self)

    if datum.default_value is None and datum.value is None :
      raise ValueError, 'default_value or value must be defined for datum %r' % datum.name
    if datum.value is None :
      datum.value = datum.default_value
    elif datum.default_value is None :
      datum.default_value = datum.value

    GuiSelector.__init__(self, datum, custom=custom)
    self.setEditable(False)
    self.connect(self, SIGNAL('currentIndexChanged(int)'),
                 self.onCurrentIndexChanged)
    self.refresh()

  def refresh(self):
    value = self._datum.value
    self.clear()
    shared_list = self.custom.getOption(u'shared_list', self, self._datum, [])
    shared_list += self._datum.value_list
    self._items = list(set(shared_list))
    self._items.sort()
    for elem in self._items :
      self.addItem(self.toUnicode(elem))

    self.setValue(value)

  def onCurrentIndexChanged(self, idx):
    value = self._items[idx]
    self._valueChanged(value)

  def setValueList(self, value_list):
    self._datum.value_list = value_list

  def setValue(self, value):
    idx = self.findText(self.toUnicode(value))
    if idx >= 0 :
      value = self._datum.value = self._items[idx]
      self.setCurrentIndex(idx)

  def toUnicode(self, value):
    try :
      alias = self._datum.getValueAlias(value)
    except (AttributeError, KeyError) :
      alias = value
    return unicode3(alias)

  def fromUnicode(self, alias):
    try :
      value = self._datum.getValueAlias(alias)
    except (AttributeError, KeyError) :
      value = alias
    return unicode3(value)

  @classmethod
  def paint_cell(cls, view, painter, option, index, datum) :
    return paint_unicode(view, painter, option, index, datum)

class GuiRadioUnicodeSelector (QtGui.QWidget, GuiSelector):

  nature = unicode
  valueChanged = QtCore.Signal(object)

  def __init__(self, datum, custom=DEFAULT_CUSTOM):
    QtGui.QWidget.__init__(self)

    self._buttons = {}
    if datum.default_value is None and datum.value is None :
      raise ValueError, 'default_value or value must be defined for datum %r' % datum.name
    if datum.value is None :
      datum.value = datum.default_value
    elif datum.default_value is None :
      datum.default_value = datum.value

    GuiSelector.__init__(self, datum, custom=custom)

    self.layout_main = QtGui.QHBoxLayout(self)
    self.layout_main.setContentsMargins(0, 0, 0, 0)
    self.button_group = QtGui.QButtonGroup()

    for elem in datum.value_list :
      button = QtGui.QRadioButton(self.toUnicode(elem))
      button.setSizePolicy(HSMALL)
      self.connect(button, SIGNAL('clicked()'),
                   self.onButtonClicked)
      self._buttons[button] = elem
      self.layout_main.addWidget(button)
      self.button_group.addButton(button)

    self.setValue(datum.value)

  def onButtonClicked(self):
    value = self._buttons[self.sender()]
    self._valueChanged(value)

  def setValue(self, value):
    for button, bvalue in self._buttons.items() :
      if bvalue == value :
        self._datum.value = value
        button.setChecked(True)
        break

  def toUnicode(self, value):
    try :
      alias = self._datum.getValueAlias(value)
    except (AttributeError, KeyError) :
      alias = value
    return unicode3(alias)

  def fromUnicode(self, alias):
    try :
      value = self._datum.getValueAlias(alias)
    except (AttributeError, KeyError) :
      value = alias
    return unicode3(value)

  @classmethod
  def paint_cell(cls, view, painter, option, index, datum) :
    return paint_unicode(view, painter, option, index, datum)

class GuiRadioBoolSelector (GuiRadioUnicodeSelector):

  nature = bool

  def __init__(self, datum, custom=DEFAULT_CUSTOM):
    datum.value_list = [True, False]
    if datum.value is None :
      datum.value = False
    if datum.default_value is None :
      datum.default_value = False
    GuiRadioUnicodeSelector.__init__(self, datum, custom=custom)

  def toUnicode(self, value):
    try :
      alias = self._datum.getValueAlias(value)
    except (AttributeError, KeyError) :
      alias = unicode(value)
    return unicode3(alias)

  def fromUnicode(self, alias):
    try :
      value = self._datum.getValueAlias(alias)
    except (AttributeError, KeyError) :
      value = alias
    return value

class GuiDefaultSelector(GuiUnicodeSelector):
  pass


class GuiPathSelector (GuiUnicodeSelector):

  nature = Path

  def __init__(self, datum, editable=True, check=None,
               custom=DEFAULT_CUSTOM):
    QtGui.QLineEdit.__init__(self)
    GuiSelector.__init__(self, datum, custom=custom)

    self.check = self.custom.getOption(u'func_check', self, datum, check)

    self.connect(self, SIGNAL('editingFinished()'), self.onEditingFinished)
    editable = self.custom.getOption(u'editable', self, datum, editable)
    self.setEditable(editable)

    shared_list = self.custom.getOption(u'shared_list', self, datum, [])
    if self._datum.value_list :
      shared_list += self._datum.value_list
    shared_list = [self.toUnicode(elem) for elem in set(shared_list)]

    if shared_list :
      self.completer = QtGui.QCompleter(shared_list)
      self.completer.setCompletionMode(QtGui.QCompleter.PopupCompletion)
      self.setCompleter(self.completer)

class GuiCoordinateSelector (QtGui.QWidget, GuiSelector):

  nature = Coordinate
  valueChanged = QtCore.Signal(object)

  def __init__(self, datum, custom=DEFAULT_CUSTOM):
    QtGui.QWidget.__init__(self)

    sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
    sizePolicy.setHorizontalStretch(0)
    sizePolicy.setVerticalStretch(0)
    sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
    self.setSizePolicy(sizePolicy)
    self.setMaximumSize(QtCore.QSize(200, 16777215))
    self.layout = QtGui.QHBoxLayout(self)
    self.layout.setSpacing(2)
    self.layout.setSizeConstraint(QtGui.QLayout.SetMaximumSize)
    self.layout.setContentsMargins(0, 0, 0, 0)

    self.x = QtGui.QLineEdit(self)
    self.y = QtGui.QLineEdit(self)
    self.z = QtGui.QLineEdit(self)

    self.fields = [self.x, self.y, self.z]
    self.labels = [u'X', u'Y', u'Z']
    self.validator = QtGui.QDoubleValidator(self)

    for field in self.fields :
      sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
      sizePolicy.setHorizontalStretch(0)
      sizePolicy.setVerticalStretch(0)
      sizePolicy.setHeightForWidth(field.sizePolicy().hasHeightForWidth())
      field.setSizePolicy(sizePolicy)
      field.setMaximumSize(QtCore.QSize(50, 16777215))
      self.layout.addWidget(field)

      field.setValidator(self.validator)
      self.connect(field, SIGNAL('editingFinished()'), self.onEditingFinished)

    GuiSelector.__init__(self, datum, custom=custom)


  def setValue(self, value):
    self._datum.value = value
    if value is None :
      self.undef()
      for i, l in enumerate(self.fields):
        if not unicode(l.text()).strip() :
          l.setPlaceholderText(self.labels[i])
    else :
      self.ok()
      for i, l in enumerate(self.fields):
        l.setText(unicode(value[i]))

  def onEditingFinished(self):
    xyz = []
    OK = True
    for i, l in enumerate(self.fields) :
      text = unicode(l.text())
      if text.strip() :
        xyz.append(float(text))
      else :
        OK = False
        if not unicode(l.text()).strip() :
          l.setPlaceholderText(self.labels[i])
    if OK :
      value = Coordinate(xyz)
    else :
      value = None
    self._valueChanged(value)

  @classmethod
  def paint_cell(cls, view, painter, option, index, datum) :
    return paint_unicode(view, painter, option, index, datum)


class GuiFloatSelector(GuiUnicodeSelector):

  nature = float

  def toUnicode(self, text):
    if text is None :
      return u''
    elif isinstance(text, (float, int)) :
      return u'%.8g' % text
    elif unicode(text).strip() :
      text = unicode(text).replace(u',', u'.').replace(u' ', u'')
      return u'%.8g' % float(text)
    else :
      return u''

  def fromUnicode(self, text):
    if text is None or not unicode(text).strip() :
      return None
    else :
      return float(unicode(text).strip())

def paint_parameter(datum, painter, option, widget) :
  style = QtGui.QApplication.style()
  opt = QtGui.QStyleOptionButton()
  opt.state = QtGui.QStyle.State_On if datum.value else QtGui.QStyle.State_Off
  opt.rect = option.rect
  style.drawControl(QtGui.QStyle.CE_CheckBox, opt, painter, None)

def paint_NA(datum, painter, option) :

  painter.save()

  rect = option.rect
  rect.adjust(+5, 0, -5, 0)

  pen = QtGui.QPen()
  pen.setColor(QtCore.Qt.black)
  pen.setWidth(3)
#  pen.setStyle(data)
  painter.setPen(pen)

  middle = (rect.bottom() + rect.top()) / 2

  painter.drawLine(rect.left(), middle, rect.right(), middle)
  painter.restore()





#
#def make_chooser(datum, custom=DEFAULT_CUSTOM):
#  u"""
#  make_chooser provide view for datum.
#  Like in Model/View programming, changing model automatically update
#  view.  
#  """
#  global DEFAULT_CHOOSERS
#
#  from pyLot.application import APPLICATION
#  pm = APPLICATION.getPluginManager()
#  DEFAULT_CHOOSERS = DEFAULT_CHOOSERS + pm.get_extensions('pyLot.qtgui.choosers')
#  for chooser in DEFAULT_CHOOSERS :
#    CHOOSERS[chooser.nature] = chooser
#
#  try :
#    chooser_class = CHOOSERS[datum.nature]
#  except KeyError :
#    chooser_class = None
#
#  chooser_class = custom.getOption(u'chooser', None, datum, chooser_class)
#  if chooser_class :
#    return chooser_class(datum, custom=custom)
#  else :
#    return None

DEFAULT_SELECTORS = [
  GuiIntSelector,
  GuiBoolSelector,
  GuiUnicodeSelector,
  GuiFloatSelector,
  GuiCoordinateSelector,
  GuiPathSelector,
  GuiPathSelector,
  GuiPathSelector,
  ]

SELECTORS = {}
for selector in DEFAULT_SELECTORS :
  SELECTORS[selector.nature] = selector


def make_selector(datum, custom=DEFAULT_CUSTOM):
  u"""
  make selector returns a QWidget to edit datum value.
  Widget type is adapted to Datum type. 
  For example, make_selector(Datum(u'i', int, 1)) will return a SpinBox like
  chooser.
  
  make_selector use maximum of information provided by datum (for example
  value, default_value and value list) to construct widget but do not ensure
  datum -> widget synchronization : changing datum field do not change widget !!
  Changing widget value change datum value.
  
  If you want widget and datum synchronization, you should use "choosers" 
  instead.
  
  In contrary to common QWidget, selector also handle special case "None"
  which means "not defined".
  Be careful not to confuse between "None" and "empty/null"
  """
  global DEFAULT_SELECTORS

  from pyLot.application import APPLICATION
  pm = APPLICATION.getPluginManager()
  DEFAULT_SELECTORS = DEFAULT_SELECTORS + pm.get_extensions('pyLot.qtgui.selectors')
  for selector in DEFAULT_SELECTORS :
    SELECTORS[selector.nature] = selector

  try :
    selector_class = SELECTORS[datum.nature]
  except KeyError :
    selector_class = GuiDefaultSelector

  selector_class = custom.getOption(u'selector', None, datum, selector_class)
  return selector_class(datum, custom=custom)


def get_paint_function(datum, custom=DEFAULT_CUSTOM):
  try :
    selector_class = SELECTORS[datum.nature]
  except KeyError :
    selector_class = GuiDefaultSelector
  selector_class = custom.getOption(u'selector', None, datum, selector_class)

  try :
    painter = selector_class.paint_cell
  except AttributeError :
    return None
  else :
    return painter
